//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<CLocalFileSystem.cpp>
///	@path	~/src/database/xres/
///	@date	2007/11/24
///	@desc	.

#define _FULLAPI
#include "config/config.h"

#include "lib/utilities/string.h"

#include "lib/system/sysinfo.h"
#include "lib/system/file_op.h"

#include "database/IData.h"
#include "database/store/IFileHandler.h"
#include "database/xres/CLocalFile.h"
#include "database/xres/CLocalFileSystem.h"

namespace xeres {

	namespace
	{
		/*!
			\class	FileStream
			\brief	Implements an IStream for local file.
		*/
		class FileStream : public ::IStream
		{
		private:
			/// \ctor
			FileStream( HANDLE hFile , const wchar_t * name )
				: _hFile( hFile )
			{
				_refcount = 1;
			}

			/// \dtor
			~FileStream()
			{
				if (_hFile != INVALID_HANDLE_VALUE)
				{
					::CloseHandle(_hFile);
				}
			}

		public:

			HRESULT static OpenFile(LPCWSTR pName, IStream ** ppStream, bool fWrite)
			{
				HANDLE hFile = ::CreateFileW(pName, fWrite ? GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ,
					NULL, fWrite ? CREATE_ALWAYS : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

				if (hFile == INVALID_HANDLE_VALUE)
					return HRESULT_FROM_WIN32(GetLastError());

				*ppStream = new FileStream( hFile , pName );

				if(*ppStream == NULL)
					CloseHandle(hFile);

				return S_OK;
			}

			virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject)
			{ 
				if (iid == __uuidof(::IUnknown)
					|| iid == __uuidof(::IStream)
					|| iid == __uuidof(::ISequentialStream))
				{
					*ppvObject = static_cast<IStream*>(this);
					return S_OK;
				} else
					return E_NOINTERFACE; 
			}

			virtual ULONG STDMETHODCALLTYPE AddRef(void) 
			{ 
				return (ULONG)InterlockedIncrement(&_refcount); 
			}

			virtual ULONG STDMETHODCALLTYPE Release(void) 
			{
				ULONG res = (ULONG) InterlockedDecrement(&_refcount);
				if (res == 0) 
					delete this;
				return res;
			}

			// ISequentialStream Interface
		public:
			virtual HRESULT STDMETHODCALLTYPE Read(void* pv, ULONG cb, ULONG* pcbRead)
			{
				DWORD read = 0;
				BOOL rc = ReadFile(_hFile, pv, cb, &read, NULL);
				if( pcbRead )
					*pcbRead = read;
				return (rc) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
			}

			virtual HRESULT STDMETHODCALLTYPE Write(void const* pv, ULONG cb, ULONG* pcbWritten)
			{
				return E_NOTIMPL;
				ULONG written = 0;
				BOOL rc = WriteFile( _hFile, pv, cb, &written, NULL );
				if( pcbWritten )
					*pcbWritten = written;
				return (rc) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
			}

			// IStream Interface
		public:
			virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER)
			{ 
				return E_NOTIMPL;   
			}

			virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream*, ULARGE_INTEGER, ULARGE_INTEGER*,
				ULARGE_INTEGER*) 
			{ 
				return E_NOTIMPL;   
			}

			virtual HRESULT STDMETHODCALLTYPE Commit(DWORD)                                      
			{ 
				return E_NOTIMPL;   
			}

			virtual HRESULT STDMETHODCALLTYPE Revert(void)                                       
			{ 
				return E_NOTIMPL;   
			}

			virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)              
			{ 
				return E_NOTIMPL;   
			}

			virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD)            
			{ 
				return E_NOTIMPL;   
			}

			virtual HRESULT STDMETHODCALLTYPE Clone(IStream **)                                  
			{ 
				return E_NOTIMPL;   
			}

			virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER liDistanceToMove, DWORD dwOrigin,
				ULARGE_INTEGER* lpNewFilePointer)
			{ 
				DWORD dwMoveMethod;

				switch(dwOrigin)
				{
				case STREAM_SEEK_SET:
					dwMoveMethod = FILE_BEGIN;
					break;
				case STREAM_SEEK_CUR:
					dwMoveMethod = FILE_CURRENT;
					break;
				case STREAM_SEEK_END:
					dwMoveMethod = FILE_END;
					break;
				default:   
					return STG_E_INVALIDFUNCTION;
					break;
				}

				if (SetFilePointerEx(_hFile, liDistanceToMove, (PLARGE_INTEGER) lpNewFilePointer,
					dwMoveMethod) == 0)
					return HRESULT_FROM_WIN32(GetLastError());
				return S_OK;
			}

			virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG* pStatstg, DWORD grfStatFlag) 
			{
				memset( pStatstg , 0 , sizeof(STATSTG) );

				BY_HANDLE_FILE_INFORMATION fileinfo;
				if( ::GetFileInformationByHandle( (HANDLE)_hFile , &fileinfo ) == FALSE )
					HRESULT_FROM_WIN32( GetLastError() );
				if( ::GetFileSizeEx(_hFile, (PLARGE_INTEGER) &pStatstg->cbSize) == FALSE )
					return HRESULT_FROM_WIN32( GetLastError() );

				pStatstg->pwcsName = L"";/*(LPOLESTR)_name.c_str();*/
				pStatstg->type = STGTY_STREAM;
				pStatstg->mtime = fileinfo.ftLastWriteTime;
				pStatstg->ctime = fileinfo.ftCreationTime;
				pStatstg->atime = fileinfo.ftLastAccessTime;
				pStatstg->grfMode = 0;
				pStatstg->grfLocksSupported = 0;
				return S_OK;
			}

		private:
			HANDLE _hFile;
			LONG _refcount;
		};
	}

	IMPL_IOBJECT_CLASS( CLocalFileSystem );

	// ctor
	CLocalFileSystem::CLocalFileSystem( void )
	{
	}

	// dtor
	CLocalFileSystem::~CLocalFileSystem( void )
	{
	}

	// SetPathBase
	bool CLocalFileSystem::SetPathBase( const WString& path )
	{
		if( !m_pathBase.empty() )
		{
			TRACE_ERROR( _S("CLocalFileSystem::SetPathBase: Path base is set already.") );
			return false;
		}
		else
		{
			// test if root path is valid directory
			std::vector<WString> rootPaths = GetRootPaths();
			WString rootPath;
			for( size_t i = 0 , total = rootPaths.size() ; i < total ; ++i )
			{
				rootPath = rootPaths[i] + path;
				if( FileOp::FileExist( rootPath ) == FILE_DIR )
				{
					break;
				}
				rootPath.clear();
			}
			if( rootPath.empty() )
			{
				return false;
			}

			m_pathBase = path;
			m_fullpathBase = rootPath;

			if( m_fullpathBase[m_fullpathBase.size() - 1] != '/' )
				m_fullpathBase.push_back( '/' );

			wcs_to_mbs( m_fullpathBase , m_fullpathBaseA );

			return true;
		}
	}

	// GetRootPath
	std::vector<WString> CLocalFileSystem::GetRootPaths( void )
	{
		std::vector<WString> retval;
		retval.push_back( get_cur_dir() );
		retval.push_back( get_app_dir() );
		return retval;
	}

	// GetPathBase
	const WString& CLocalFileSystem::GetFullPathBase( void )
	{
		if( m_pathBase.empty() )
		{
			TRACE_WARNING( _S("CLocalFileSystem::GetFullPathBase: Path base is not set, use default ('/.').") );
			// set to default path base
			SetPathBase( _S("/.") );
		}
		return m_fullpathBase;
	}

	// CompositePath
	WString CLocalFileSystem::CompositePath( const WString& path )
	{
		// composite default path
		return GetFullPathBase() + path;
	}

	bool CLocalFileSystem::FileExist( const WString& path )
	{
		return FileOp::FileExist( CompositePath( path ) ) == FILE_READY;
	}

	bool CLocalFileSystem::DirectoryExist( const WString& path )
	{
		return FileOp::FileExist( CompositePath( path ) ) == FILE_DIR;
	}

	// GetFileInfo
	bool CLocalFileSystem::GetFileInfo( const WString& path , DataInfo& info )
	{
		FileOp op;
		if( FileExist( path ) )
		{
			info.m_type = DATA_FILE;
			if( op.OpenFile( CompositePath( path ) , true ) != FILE_READY )
				return false;
		}
		else if( DirectoryExist( path ) )
		{
			info.m_type = DATA_DIR;
			if( op.OpenDir( CompositePath( path ) ) != FILE_READY )
				return false;
		}
		return op.GetFileTime( info.m_created , info.m_accessed , info.m_modified );
	}

	// CreateDirectory
	bool CLocalFileSystem::CreateDirectory( const WString& path )
	{
		return FileOp::CreateDir( CompositePath( path ) ) == FILE_READY;
	}

	// CreateFile
	RefPass<IFile> CLocalFileSystem::CreateFile( const WString& path )
	{
		CLocalFile * file = new CLocalFile;
		if( file->op().CreateFile( CompositePath( path ) , false , false ) != FILE_READY )
		{
			delete file;
			return NULL;
		}
		return RefPass<IFile>(file);
	}

	// OpenFile
	RefPass<IFile> CLocalFileSystem::OpenFile( const WString& path )
	{
		CLocalFile * file = new CLocalFile;
		if( file->op().OpenFile( CompositePath( path ) , false ) != FILE_READY )
		{
			delete file;
			return NULL;
		}
		return RefPass<IFile>(file);
	}

	// CopyFile
	bool CLocalFileSystem::CopyFile( const WString& src , const WString& dst )
	{
		return FileOp::CopyFile( CompositePath( src ) , CompositePath( dst ) ) == FILE_READY;
	}

	// DeleteFile
	bool CLocalFileSystem::DeleteFile( const WString& path )
	{
		return FileOp::DeleteFile( CompositePath( path ) ) == FILE_READY;
	}

	// ListDir
	bool CLocalFileSystem::ListDir( const WString& path , const WString& spec , std::vector<WString>& files )
	{
		FileOp::ListDir( CompositePath( path ) , spec , files );
		return true;
	}

	// EnumerateFiles
	void CLocalFileSystem::EnumerateFiles( const WString& path , const WString& spec ,
		function<void(const IEnumFileData&)> handler )
	{
		FileOp::EnumerateFiles( CompositePath( path ) , spec , bind( &ProxyEnumerateFiles , &handler , _1 ) );
	}

	// ProxyEnumerateFiles
	void CLocalFileSystem::ProxyEnumerateFiles(
		function<void(const IEnumFileData&)> * handler , const FileOp::EnumFileData& efd )
	{
		IEnumFileData iefd;
		iefd.type = efd.status == FILE_DIR ? DATA_DIR : DATA_FILE;
		iefd.info.m_size = efd.size;
		iefd.info.m_created = efd.creation;
		iefd.info.m_accessed = efd.accessed;
		iefd.info.m_modified = efd.modified;
		iefd.name = efd.name;

		(*handler)( iefd );
	}

	// CreateEntryFromFile
	RefPass<IData> CLocalFileSystem::CreateEntryFromFile( const WString& path )
	{
		TRACK_FUNCTION_SCOPE();

		size_t pos = path.find_last_of( '/' ) + 1;
		WString name = path.substr( pos );
		pos = name.find_last_of( '.' ) + 1;
		WString ext = name.substr( pos );

		Key key = Key::QueryKey( ext );
		std::map< Key , Ref<IFileHandler> >::iterator it;
		if( !key.IsValid() || ( ( it = m_fileHandlers.find( key ) ) == m_fileHandlers.end() ) )
		{
			TRACE_ERROR( _S("CLocalFileSystem::CreateEntryFromFile: Handler for extension '%s' is not defined.") , ext.c_str() );
			return RefPass<IData>(NULL);
		}

		// load file
		Ref<IFile> file = CreateFile( path );
		return it->second->LoadFile( key , file );
	}

	// LoadEntryFromFile
	RefPass<IData> CLocalFileSystem::LoadEntryFromFile( const WString& path )
	{
		TRACK_FUNCTION_SCOPE();

		size_t pos = path.find_last_of( '/' ) + 1;
		WString name = path.substr( pos );
		pos = name.find_last_of( '.' ) + 1;
		WString ext = name.substr( pos );

		Key key = Key::QueryKey( ext );
		std::map< Key , Ref<IFileHandler> >::iterator it;
		if( !key.IsValid() || ( ( it = m_fileHandlers.find( key ) ) == m_fileHandlers.end() ) )
		{
			TRACE_ERROR( _S("CLocalFileSystem::CreateEntryFromFile: Handler for extension '%s' is not defined.") , ext.c_str() );
			return NULL;
		}

		// load file
		Ref<IFile> file = OpenFile( path );
		return it->second->LoadFile( key , file );
	}

	// AddFileHandler
	bool CLocalFileSystem::AddFileHandler( const WString& extensions , RefWeak<IFileHandler> handler )
	{
		TRACK_FUNCTION_SCOPE();

		// split extension into seq
		StringSeq seq;
		break_string( extensions , ';' , seq );

		if( seq.size() == 0 )
		{
			TRACE_ERROR( _S("CLocalFileSystem::AddFileHandler: No valid file extension in '%s'.") , extensions.c_str() );
			return false;
		}

		for( size_t i = 0 , total = seq.size() ; i < total ; ++i )
		{
			Key key = Key::MakeKey( seq[i] );
			if( m_fileHandlers.find( key ) != m_fileHandlers.end() )
			{
				TRACE_ERROR( _S("CLocalFileSystem::AddFileHandler: File handler <%s> has been defined.") );
				continue;
			}
			m_fileHandlers[ key ] = handler;
		}
		return true;
	}

	// OpenPosixFile
	FILE * CLocalFileSystem::OpenPosixFile( const char * path , const char * mode )
	{
		// composite file name
		size_t len = sizeof(char) * ( m_fullpathBaseA.size() + strlen(path) + 1 );
		char * fullpath = (char*)alloca( len );
		strcpy( fullpath , m_fullpathBaseA.c_str() );
		strcat( fullpath , path );
		FILE * file = fopen( fullpath , mode );
		return file;
	}

	// OpenIStream
	struct IStream * CLocalFileSystem::OpenIStream( const WString& path )
	{
		IStream * stream = NULL;
		WString fullpath = m_fullpathBase + path;
		FileStream::OpenFile( fullpath.c_str() , &stream , false );
		return stream;
	}

} // namespace xeres
